Invisible Internet Project (I2P (Invisible Internet Project) is a leader among technologies for private information transfer. Complete decentralization and independence make the I2P network architecturally complex, but unique in its kind. The article is devoted to the question: can a programmer ignorant of cryptography and networks write an application that works via I2P.
Modern implementations of I2P routers have support for the SAM (Simple Anonymous Messaging) API protocol, which allows external applications to communicate over I2P using just a few simple commands. In this article, we will consider the minimum required to start your own experiments..
A little retrospective
The history of I2P begins in the first half of the 2000s. Good software development practices did not spread actively during the videotape era. Present at that time modern concepts of interaction with external applications through comfortable interface (API) was difficult.
Initially, the network's software client provided only proxies to the outside world. A proxy, in simple terms, is an intermediary that accepts a request, such as "example.i2p", and processes it. For an external application, all internal logic is completely hidden, and the process itself resembles using a regular network.
An inexperienced reader probably does not see this as a problem, because “there are proxies - do what you want”! This is partly true - client-server applications will work as normal: the site will open, IRC chat too. But what to do with peer-to-peer technologies and how to dynamically create new user IDs so that all applications do not surf the network as one person??
In an I2P router, there is a protocol called I2CP (I2P Control Protocol), which is designed to logically separate the essence of the anonymous network router and the external application. However, not everything is as rosy as it seemed to the development team at the beginning of the 2000s: I2CP is a very cumbersome and complex protocol. For an external application to do anything meaningful, it must contain more than half of the router code. Imagine that to purchase from an online store you need to speak at least conversational Chinese!
The complexity of integrating I2P into an external program quite well explains the overload of the original Java router with all sorts of additional applications like BitTorrent and Email, which turned the I2P router from a small utility into a monolithic mammoth.
The current SAM API protocol was preceded by BOB (Basic Open Bridge). When the I2P network globally switched to more advanced cryptography (abandonment of the SHA1 algorithm), the developers of the flagship Java router decided to leave Bob in the past and not teach him new standards. Be that as it may, it seemed inappropriate to spend already scarce resources on simultaneous maintenance of two similar protocols.
Before moving on to the main part of the article, I note that in the implementation of a router in C++ (i2pd), BOB is a relevant protocol along with SAM. The PurpleI2P team does not allow it to die, updating the code (and functional) base if necessary. If you're fired up with enthusiasm, you can go with it familiarize.
Start of the lesson
There are several libraries for different programming languages that provide ready-made functions and methods for interacting with an I2P router via SAM. Their official list can be easily found on the page documentation. If you successfully write your library, consider adding it to this list.
Let's look at the principles of working with SAM using the most general example - through a terminal. For example, we use the netcat utility and a router i2pd (A Java router will work in a similar way). Good old telnet won't work for connecting to Sam because it uses two line terminator characters (\r\n
), and SAM processes the end of the message character by character \n
. Symbol \r
is not provided for by the protocol, therefore it is considered an error, which causes the work to stop.
In i2pd SAM is usually enabled by default. To verify this, open the web interface (http://127.0.0.1:7070
), in which the status of services is displayed on the main page. If SAM is still inactive, enable it via the configuration file with the parameter sam.enabled = true
. By default, it is available for requests to the following address: 127.0.0.1:7656
.
Hello hidden world
When connecting to a socket that the SAM service is listening on, you must initiate a handshake. To do this you need to write HELLO VERSION
. In response, the router returns its version of the SAM protocol. If RESULT=OK
, The handshake was successful. Also to the team HELLO VERSION
you can add a version requirement: HELLO VERSION MIN=3.0 MAX=3.3
. This will come in handy when using new features that older routers don’t know about. The practice of many people shows that version 3.0 is sufficient to implement a wide variety of tasks.
When creating a session, you must specify its nickname (id), which we will use in the future to connect to the session, as well as the keys, which will be the cryptographic identifier of the new hidden point (destination). The main requirement for a nickname is the absence of spaces, but with keys the question is somewhat more interesting. If you specify a value TRANSIENT
, Random keys will be created, which, after creating a session, will be issued as a monolithic array of information (base64 encoded), which simultaneously includes public and private keys. Since the size of the part with public and private keys is known in advance, when implemented in software, it is not difficult to decode the information and separate public keys from private ones (for example, so that they can be communicated to the interlocutor). To complete the algorithm of actions, as well as for ease of use of SAM through the terminal with manual copying of keys, I will create keys with a separate command before creating a session.
Team DEST GENERATE
can be used in this form, or with an explicit indication of the signature type. DefaultSIGNATURE_TYPE=0
- obsolete type, now recommended 7
, but in order not to complicate the perception, we will use the default signature type, that is, we will not specify it. It is not necessary to know about signature types for basic use of I2P.
After receiving the command, the router returns keys with an explicit separation of public (PUB) and private (PRIV). This is very convenient when using simple keyword parsing.
Now let's move on to creating a session. SAM supports three types of sessions: STREAM (for TCP), DATAGRAM (sending and receiving UDP packets) and RAW (a variation on the UDP theme). The most obvious and basic protocol is TCP. It is used for basic operations like transferring files and messages, so for example, let's create a STREAM session.
SESSION CREATE STYLE=STREAM ID=HabraHabr DESTINATION=БЛОК_ПРИВАТНЫХ_КЛЮЧЕЙ
Parameter STYLE
is responsible for the session type (in our case STREAM
), DESTINATION
– accepts private keys (a block of information from the section PRIV
recently generated keys), ID
– session nickname by which you can access it (in the example: HabraHabr
).
After a few seconds, the router reports the successful creation of the session. The session will live as long as the socket that created it exists. In this case, you need to work with the session through new connections to Sam. This means that the control socket, although not directly used, must exist as long as we need a specific session.
Through the new window I connect to Sam again and do a standard handshake. There are two ways to use an existing HabraHabr session: initiate a connection with another hidden node on behalf of this session, knowing its address, or connect to a local stream to wait for an incoming connection from outside. To receive messages that will be sent from the second session, connect to the stream with the command STREAM ACCEPT ID=HabraHabr
.
The first session is over. I open a new terminal window, connect to Sam and create a new session “Novosibirsk” with the only difference that I do not generate the keys separately, but specify TRANSIENT
In the parameter DESTINATION
. The essence of what is happening does not change.
To connect to the previous HabraHabr session, I open another terminal window, connect to Sam and enter the command:
STREAM CONNECT ID=Novosibirsk DESTINATION=ПУБЛИЧНЫЕ_КЛЮЧИ_СЕССИИ_HabraHabr
When a message is incoming, a block of public keys of the connected subscriber arrives on the socket, which is his intranet identifier (in the screenshot on the left). This information is not needed in the current session, but it may be useful to later establish a connection with the subscriber on our initiative.
This way the two endpoints are connected. Despite the fact that they are actually located on the same I2P router, the connection passes through several computers around the world, with the connection in each direction carried out through a separate chain of transit nodes. Sometimes this can be clearly seen when sending messages quickly from one terminal window to another and taking a long time to deliver them in the opposite direction..
What is shown in the example as a primitive text chat is, at its core, a direct connection between two subscribers through a hidden network. A similar P2P (peer-to-peer) connection can be used to communicate over a variety of protocols without using an intermediary server and maintaining the confidentiality of both parties.
When one of the interlocutors disconnects, the user on the other end will also be disconnected from their session. The manipulations of the interlocutor on the other end of the line do not affect the local session, but each time it is necessary to reconnect to it from a new socket in order to catch a new incoming connection. We can say that each connection is one-time. In the case of the terminals example, this means that each connection requires a new terminal window with the connection to Sam in standby mode.
This is especially important to keep in mind in practical implementation: it is necessary to create a new connection to the local session every time an existing one accepts an incoming connection. It is also possible to have several listening sockets at the same time, which will prevent connection denial for a user for whom we did not have time to create a new listening connection.
Summary
This article demonstrates the basic principles of the work that your application can perform directly, or by wrapping the same commands in the form of a library. If the topic turns out to be in demand, we will analyze SAM in more detail.